package editorsvg.propiedades;

import editorsvg.editor.comandos.ComandoCambiarPropiedad;
import editorsvg.editor.comandos.GestorComandos;
import editorsvg.gui.generico.Actualizable;
import java.lang.reflect.Method;
import javax.swing.JComponent;

/**
 * Clase base para la implementeción de las propiedades.
 *
 * @author Jorge Berjano
 */
public abstract class Propiedad implements Cloneable {    
    private String nombre;
    private String alias;
    private Object objeto;    
    private GestorComandos gestorComandos;
    
    private String nombreMetodoSet;
    private String nombreMetodoGet;
    
    private Object valorLocal; // Valor de las propiedades que no se asignan con reflexión
    
    /** Constructor por defecto. */
    public Propiedad() {
    }
    
    public Propiedad(Object objeto, String nombre) {
        this(objeto, nombre, nombre);
    }
        
    public Propiedad(Object objeto, String nombre, String alias) {
        this();        
        setObjeto(objeto);
        setNombre(nombre);
        setAlias(alias);
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        Propiedad clon = (Propiedad) super.clone();
        clon.objeto = null;
        return clon;
    }
    
    @Override
    public String toString() {
        return getValor().toString();
    }

    public Object getObjeto() {
        return objeto;
    }

    public void setObjeto(Object objeto) {
        this.objeto = objeto;
    }
    
    public String getNombre() {
        return nombre;
    }
    
    public void setNombre(String nombre) {
        this.nombre = nombre;
        
        if (!nombre.isEmpty()) {
            StringBuilder bs = new StringBuilder(nombre);       
            bs.setCharAt(0, Character.toUpperCase(bs.charAt(0)));
            nombreMetodoGet = "get" + bs.toString();
            nombreMetodoSet = "set" + bs.toString();
        }        
    }

    public String getAlias() {
        return alias;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }        
    
    public Object getValor() {        
        if (objeto == null) {            
            return valorLocal;
        }
        
        Class clase = objeto.getClass();
        try {
            Method metodoGet = clase.getMethod(nombreMetodoGet);
            return metodoGet.invoke(objeto);            
        } catch (Exception ex) {
            System.err.println("No se puede leer " + nombre);
            ex.printStackTrace();
        }
        
        return null;
    }
    
    public abstract Class getClase();
    
    /**
     * Establece el valor de la propiedad. Si se ha definido un gestor de comandos
     * la asignación se ejecuta a traves de él, actualizando el objeto.
     * En otro caso simplemente se asigna el valor.
     */
    public void setValor(Object valor) {
        if (objeto == null) {
            valorLocal = valor;
            return;
        }
        
        Object valorActual = getValor();
        boolean iguales = valorActual == null ? valor == null : valorActual.equals(valor);
        if (iguales) {
            return;
        }
        
        if (gestorComandos != null) {
            ComandoCambiarPropiedad comando = new ComandoCambiarPropiedad(this, valor, valorActual);        
            gestorComandos.ejecutar(comando);
        } else {
            asignar(valor);
        }
    }
    
    /**
     * Asigna un valor a la propiedad.
     * @return True si se ha modificado el valor que tenía la propiedad.
     */
    private boolean asignar(Object valor) {
        if (sonIguales(valor, getValor())) {
            return true;
        }
        
        Class clase = objeto.getClass();
        Class[] clasesParametros = new Class[1];
        clasesParametros[0] = getClase();
        Method metodoSet;
        try {
            metodoSet = clase.getMethod(nombreMetodoSet, clasesParametros);
            metodoSet.invoke(objeto, valor);
        } catch (Exception ex) {
            System.err.println("No se puede escribir en " + nombre);
            ex.printStackTrace();
            return false;
        }
        return true;
    }
    
    /**
     * Ejecuta la asignación. Normalmente es llamada por el gestor
     * de comandos.
     */
    public void ejecutarAsignacion(Object valor) {
        
        if (!asignar(valor)) {
            return;
        }       
    
        if (objeto instanceof Actualizable) {
            ((Actualizable) objeto).actualizar();
        }
    }
    
    public boolean parsearPropiedad(String cadena) {
        return false;
    }
    
    
    public boolean permitirEditarTexto() {
        return true;
    }
    
    public boolean permitirPulsarBoton() {
        return true;
    }
    
    public JComponent obtenerComponenteModificacion() {
        return null;
    }

    public void setGestorComandos(GestorComandos gestorComandos) {
        this.gestorComandos = gestorComandos;
    }

    private static boolean sonIguales(Object objeto1, Object objeto2) {
        return objeto1 == null ? objeto2 == null : objeto1.equals(objeto2);
    }
}
